home *** CD-ROM | disk | FTP | other *** search
/ PsL Monthly 1993 December / PSL Monthly Shareware CD-ROM (December 1993).iso / prgmming / dos / basic / tutorqb.com / TUTOR-04.BAS < prev    next >
Encoding:
BASIC Source File  |  1986-12-06  |  21.4 KB  |  446 lines

  1. goto example1
  2. '                       The QuickBASIC Tutorial Series
  3.  
  4. '                                   Part 4
  5. '                             File Input/Output
  6.  
  7. '                        Copyright 1986  Ford Software
  8.  
  9. '                            4845 Willowbend Blvd.
  10. '                              Houston, TX 77035
  11. '                                (713) 721-5205
  12. '                          CompuServe ppn: 71355,470
  13.  
  14. 'This Tutorial may not be copied or distributed in any form - printed, on disk
  15. 'or electronically - without the express written permission of Ford Software.
  16. '          Unlicensed distribution is a violation of copyright law
  17. '           and may be subject to civil and criminal prosecution.
  18.  
  19. 'QuickBASIC is a trademark of the Microsoft Corporation.
  20. 'Ford Software is not associated in any way with the Microsoft Corporation.
  21.  
  22. '(Make sure NumLock is off and press PgDn)
  23. 'page 2                     I N T R O D U C T I O N
  24.  
  25. 'I can remember file input/output programming being the most difficult part
  26. 'of BASIC programming to grasp for reasons I can no longer remember.  One
  27. 'reason may have been that I was trying to learn it from an operating system
  28. 'reference manual (Radio Shack's TRS-DOS manual), instead of from a tutorial.
  29.  
  30. 'Hopefully, we can approach the subject in a way that will make things easier
  31. 'to understand. Remember to keep the QB2 manual open to the subject we are
  32. 'discussing for help with topics we may not cover in enough detail for you.
  33.  
  34. 'Also remember how to run examples of code: press Ctrl-PgUp to get to the
  35. 'start of this file. Change that first line to read "goto example#" for the
  36. 'example number you want to run. Return to the page you were on and press
  37. 'Ctrl-R to compile and run the example.
  38.  
  39. 'DEFINITIONS: A "field" is a single item of information, such as "Address".
  40. '             A "record" is a set of fields, such as:
  41. '               First Name, Last Name, Address, City, State, Zip, Telephone #
  42.  
  43.  
  44. '(Press PgDn)
  45. 'page 3
  46. '                                 Contents
  47.  
  48. '                 page
  49.  
  50. '                  4   The FILES Command
  51. '                  5   Types of Files
  52. '                  6   OPEN - Sequential
  53. '                  7   Writing to a Sequential File
  54. '                 11   Random Access Files
  55. '                 12   The FIELD and GET Statements
  56. '                 14   LSET / RSET
  57. '                 15   More about FIELDing
  58. '                 17   Opening Non-Data Files
  59. '                 18   Compressing Numbers
  60. '                 19   EOF, LOF and LOC
  61.  
  62.  
  63. '                To search for specific text, use Ctrl-F.
  64.  
  65.  
  66. '(Press PgDn)
  67. 'page 4                        The FILES Command
  68.  
  69. 'The FILES Command is similar to DOS's DIR command with the /W (wide display)
  70. 'option specified. That is, FILES shows the files in four columns and without
  71. 'showing the file time, date or size.
  72. example1:
  73. FILES
  74. INPUT "Ready for FILES *.BAS"; X$
  75. FILES "*.bas"
  76. INPUT "Ready for FILES TUTOR-??.*"; X$
  77. FILES "TUTOR-??.*"
  78. INPUT "Ready to see FILES use a variable"; X$
  79. Y$="tutor-"
  80. FILES Y$+"*.BAS"
  81. INPUT "Ready for a FILES error message"; X$
  82. FILES Y$+"??"     'After getting the runtime error message and returning to
  83. END               'this screen, press Alt-V, E, Enter to remove the Error box.
  84. 'As you can see, if the specified file is not found, the program aborts all
  85. 'the way back to DOS. In a later lesson, we will see how to get around this
  86. 'problem so that your program can continue running if a file isn't found.
  87.  
  88. '     (PgDn)
  89. 'page 5                         Types of Files
  90.  
  91. 'Generally speaking, there are two broad types of files, program files and
  92. 'data files.  Either type of file can be modified from within BASIC. For
  93. 'example, if you have written a program and compiled it into an "EXE" file,
  94. 'you could open the "EXE" file as if it were a data file and imbed a serial
  95. 'number or your customer's name in it without having to change it in the
  96. 'source code and recompiling it.
  97.  
  98. 'There are four things you can do with a file: open the file, write data to
  99. 'the file, read data from the file, and close the file. When you write to or
  100. 'read from the file, the computer does not physically go to the disk each
  101. 'time. Instead, the computer uses some memory to store the data until it gets
  102. 'to be enough to make the trip worthwhile or until the file is going to be
  103. 'closed or some other factor causes the data to be written. This area of
  104. 'memory is called a "buffer". If you have three files open at the same time,
  105. 'each one must have its own buffer area. Read in your DOS manual about setting
  106. 'up file buffer areas in your CONFIG.SYS file.
  107.  
  108. 'There are two ways to access files, sequentially or randomly. We tend to
  109.  
  110. '     (PgDn)
  111. 'page 5b                       (Types of Files)
  112.  
  113. 'think of a file as being "sequential" or "random", depending on certain
  114. 'characteristics, but any file can be opened for either access method, no
  115. 'matter how the file was originally opened and written to. If we sometimes
  116. 'refer to a "random access file" or a "sequential file", we are referring to
  117. 'the way it is normally opened, written to, and read from.
  118.  
  119. 'In addition, in normal usage, the two different types of files will have
  120. 'specific characteristics that will distinguish them.
  121.  
  122. 'Sequential files can have records of varying lengths because a record is
  123. 'defined as a series of characters ending with a carriage return and linefeed.
  124. 'The fields within a record are seperated by "delimiters" (commas, quotes).
  125.  
  126. 'Random access files normally have records of the same length. This makes it
  127. 'easy for the system to find any record by multiplying the record number times
  128. 'the record length. Fields in a record are also defined by length, so delimit-
  129. 'ers are not required. We will now look at these two types of files in detail.
  130.  
  131.  
  132. '     (PgDn)
  133. 'page 6                        OPEN - Sequential
  134.  
  135. 'Sequential files must be opened for reading or writing only and can only be
  136. 'read from or written to in sequential order. An important point is that when
  137. 'you open a file for sequential output, if a file with that name already
  138. 'exists, it is erased. It is up to you to make sure that an existing file is
  139. 'not erased in this manner. An exception is that you can open a file to append
  140. 'data to the end of the file. Here are some example of sequential OPENs. Be
  141. 'sure to read the OPEN Statement in the QB2 manual.
  142.  
  143. example2:
  144. filename$="test1"
  145. buffernum=1
  146. OPEN filename$ FOR OUTPUT AS buffernum   'open a file for sequential writing.
  147. CLOSE
  148. OPEN filename$ FOR INPUT AS buffernum    'open a file for sequential input.
  149. OPEN "test2" FOR OUTPUT AS 2             'open a second file for output.
  150. CLOSE 2                                  'close only the second file.
  151. OPEN filename$ FOR APPEND AS buffernum   'open a file to add data to the end.
  152. CLOSE                                    'closes both files 1 and 2.
  153.  
  154. '     (PgDn)
  155. 'page 7                   Writing to a Sequential File
  156.  
  157. 'There are two commands for writing to a sequential file: PRINT # and WRITE #.
  158. 'The PRINT #n command is very similar to the PRINT command that writes data to
  159. 'the screen, even to the extent of having a "PRINT #n USING" option to format
  160. 'data into the file like PRINT USING does to the screen. In fact, this may be
  161. 'a good time to point out that the screen can be OPENed as an output device
  162. 'and written to sequentially just like a disk file.
  163.  
  164. 'To understand the finer points of PRINTing data to a file, you need to first
  165. 'understand how QB2 reads data in from a sequential file using INPUT #. Again,
  166. 'the INPUT #n command is similar to the INPUT command used to get input from
  167. 'the user. This means that commas and quote marks are used to seperate data
  168. 'items in the input field. To review how INPUT from the keyboard works, say
  169. 'you have a line like:
  170. '            INPUT "Enter name, age, weight, address: ", N$, A, W, A$
  171. 'If the user enters:  Bill Will, 30, 150, 1234 Elm Street
  172. 'everything is fine. The four fields are "delimited" by commas.
  173. 'If he enters:  Bill Will, 30, 150, Houston, TX  there is a problem because
  174. 'it looks like five data items to INPUT, so the user must put the address in
  175. 'quotes:        Bill Will, 30, 150, "Houston, TX"
  176. '     (PgDn)
  177. 'page 8              (Writing to a sequential file, cont.)
  178.  
  179. 'If you start text with a double quote mark ("), everything that follows up to
  180. 'the next double quote mark is assigned to one variable. For example, if you
  181. 'enter:  "Big D" Dallas, 30   in response to INPUT "Address, age"; A$, A
  182. 'the computer will see this as three variables. You can, however, have quotes
  183. 'within a response. For example:    New "The Big Apple" York, 20
  184. 'Run the following example and try different responses with commas and quotes
  185. 'to get a feel for how they are treated. Enter   quit, 0, 0, x   to end.
  186. example3:
  187. WHILE N$<>"quit"
  188.   INPUT "Enter name, age, weight, address: ", N$, A, W, A$
  189.   PRINT N$, A, W, A$
  190. WEND : END
  191.  
  192. 'Now, knowing that INPUT will want to see   Bill Will, 30, 150, "Houston, TX"
  193. 'in the file with the quote marks as shown, how do you put the quote marks
  194. 'into the file? If you say   PRINT "Houston, TX", 40   you know the quotes
  195. 'will not appear on the screen. Neither will they appear in the file. You must
  196. 'enter the ASCII code for the quote mark, which is CHR$(34):
  197. '       PRINT #1, "Bill Will", 30, 150, CHR$(34) "Houston, TX" CHR$(34)
  198. '     (PgDn)
  199. 'page 9              (Writing to a sequential file, cont.)
  200.  
  201. 'The other problem is that the comma will not appear in the file either, so
  202. 'the PRINT line just shown is not correct either. However, while you can't put
  203. 'a quote mark between quotes ("""), you can put a comma between quotes (",").
  204. example4:
  205. OPEN "test4" FOR OUTPUT AS #1
  206. PRINT #1, "Bill Will," 30 "," 150 ",Houston, TX"
  207. PRINT #1, "Bill Will," 30 "," 150 "," CHR$(34) "Houston, TX" CHR$(34)
  208. CLOSE
  209. OPEN "test4" FOR INPUT AS #1
  210. PRINT "This is how the data actually looks in the file:"
  211. LINE INPUT #1, A$ : LINE INPUT #1, B$
  212. PRINT A$ : PRINT B$ : PRINT
  213. CLOSE
  214. OPEN "test4" FOR INPUT AS #1
  215. PRINT "This is how the data gets loaded into the variables N$, A, W, A$:"
  216. INPUT #1, N$, A, W, A$ : PRINT N$, A, W, A$ : INPUT #1, missed.field$
  217. INPUT #1, N$, A, W, A$ : PRINT N$, A, W, A$
  218. PRINT "Notice that 'TX' didn't get into the variable A$ in the first line"
  219. PRINT "because the comma says that TX is a seperate variable.":  CLOSE: end
  220. '     (PgDn)
  221. 'page 10             (Writing to a sequential file, cont.)
  222.  
  223. 'There is an easy way to write data to a sequential file without having to
  224. 'worry about commas and quotes. Use the WRITE command, which automatically
  225. 'delimits variables with commas and quotes.
  226. example5:
  227. OPEN "test5" FOR OUTPUT AS #1
  228. PRINT #1, "Bill Will", 30, 150, "Houston, TX"
  229. WRITE #1, "Bill Will", 30, 150, "Houston, TX"
  230. CLOSE : OPEN "test5" FOR INPUT AS #1
  231. PRINT "This is how the data actually looks in the file:"
  232. LINE INPUT #1, A$: PRINT A$ : LINE INPUT #1, A$: PRINT A$
  233. CLOSE: OPEN "test5" FOR INPUT AS #1
  234. PRINT "We saw in Example4 that the first line will not load back correctly:"
  235. INPUT #1, N$, A$: PRINT "N$ = "N$: PRINT "A = "A: PRINT "W =": PRINT "A$ ="
  236. PRINT "All data up to the first comma was assigned to the first varible."
  237. PRINT "So 'TX' is read into the variable 'A'."
  238. PRINT "The next line, stored with the WRITE statement, is properly delimited:"
  239. INPUT #1, N$, A, W, A$
  240. PRINT "N$ = "N$ : PRINT "A = " A : PRINT "W = " W : PRINT "A$ = " A$
  241. CLOSE: end
  242. '     (PgDn)
  243. 'page 11                      Random Access Files
  244.  
  245. 'As we have said, any file can be opened for random access. However, in order
  246. 'to make sense of the data, it needs to be arranged into fixed-length fields
  247. 'in fixed-length records. The reason why is that when you open a file for
  248. 'random access, you define how many characters are read into a particular
  249. 'variable, unlike sequential files that rely on commas, quotes and carriage
  250. 'returns to determine what data goes into a particular variable.
  251.  
  252. 'Examples of records in a variable-record-length (sequential) file:
  253. 'Bill Will, 30, 150, "Houston, TX"
  254. 'Roger Bovine, 22, 180, "Summerfield, LA"
  255.  
  256. 'Example of records in a fixed-record-length (random access) file:
  257. 'Bill Will                30150Houston, TX    Roger Bovine             22180Su
  258. 'mmerfield, LA
  259.  
  260. 'In the fixed-record-length file, not only are delimiters not requred, you
  261. 'need not even leave spaces between the fields. You will also notice that
  262. 'there is no carriage return/line feed between records, so if you TYPE the
  263. 'file in DOS, you will see the records running together.
  264. '     (PgDn)
  265. 'page 12                  The FIELD & GET Statements
  266.  
  267. 'You get the data in a random access file into the proper variables by using
  268. 'the FIELD statement. Using the data from the preceeding page as an example:
  269. 'OPEN "DATAFILE" AS #1 LEN=45   'each record is 45 characters long
  270. 'FIELD #1, 25 AS NAM$, 2 AS AGE$, 3 AS WT$, 15 AS ADDR$
  271.  
  272. 'You can now retrieve any record in the file with the GET command. GET #1, 7
  273. 'retrieves the second record. It's location in the file is easily calculated
  274. 'by the system: LOC=REC.LEN*(REC.NUM-1)+1. In this case: 45*(7-1)+1=271, so
  275. 'the seventh record starts at the 271st character in the file.
  276.  
  277. 'If you count the characters in the data on the preceeding page, you will see
  278. 'that the data in the two run-on records fit the above FIELD statment. Blanks
  279. 'between fields become part of the variable. For example, if you say GET 1,1
  280. 'to retrieve the first record, the variable NAM$ will contain 25 characters,
  281. 'consisting of the name Bill Will and 16 blank spaces.
  282.  
  283. 'All fielded variables must be strings, so AGE$ would be "30". To convert it
  284. 'to a numeric variable, you would have to say AGE%=VAL(AGE$).
  285.  
  286. '     (PgDn)
  287. 'page 13                       The LSET Command
  288.  
  289. 'In the previous example, the first name and last name were lumped into one
  290. 'variable, as were city and state, just to maintain continuity with examples
  291. 'of sequential files. Normally, first and last names in a random file would
  292. 'be seperated, as would city and state. Here is a more realistic example:
  293. example6:
  294. OPEN "test6" AS #1 LEN=45  'open a file for random I/O (Input/Output)
  295. FIELD #1, 10 AS FIRST.NAME$, 15 AS LAST.NAME$, 2 AS AGE$, 3 AS WT$,_
  296.       13 AS CITY$, 2 AS STATE$   'define the fields
  297. LSET FIRST.NAME$="Roger": LSET LAST.NAME$="Bovine": LSET AGE$="22"
  298. LSET WT$="180": LSET CITY$="Summerfield": LSET STATE$="LA"
  299. PUT #1, 2   'just for grins, save the second record first
  300. LSET FIRST.NAME$="Bill": LSET LAST.NAME$="Will": LSET AGE$="30"
  301. LSET WT$="150": LSET CITY$="Houston": LSET STATE$="TX"
  302. PUT #1, 1   'save the first record
  303. GET #1, 2   'get the second record and print it
  304. PRINT FIRST.NAME$ " " LAST.NAME$ " " AGE$ " " WT$ " " CITY$ ", " STATE$
  305. FIELD #1, 45 AS WHOLE.REC$ : PRINT WHOLE.REC$ : CLOSE #1 : END
  306. '  (The next several pages will discuss the code above. Continue reading.)
  307.  
  308. '     (PgDn)
  309. 'page 14                          LSET / RSET
  310.  
  311. 'The LSET and RSET commands are used to assign data to a FIELDed variable.
  312. 'Most of the time you will use LSET, which left-justifies the data in the
  313. 'field and pads the rest of the field with blands. RSET right-justifies the
  314. 'data in the field and pads the beginning of the field with blanks.
  315.  
  316. '=============================== W A R N I N G ===============================
  317. '  The single most common mistake in dealing with FIELDed variables is to
  318. '  assign data to a FIELDed variable without using LSET or RSET. BASIC does
  319. '  not consider this an error and will change the value of the variable,
  320. '  but any GET's that follow WILL NOT CHANGE THE VALUE OF THE VARIABLE:
  321. example7:  '(The file from example6 should already be on your disk.)
  322. OPEN "test6" AS #1 LEN=45
  323. FIELD #1, 10 AS FIRST.NAME$, 15 AS LAST.NAME$
  324. GET 1,1 : PRINT FIRST.NAME$ " " LAST.NAME$
  325. FIRST.NAME$="we goofed"    'no LSET/RSET used; GET won't work right now.
  326. GET 1,1 : PRINT FIRST.NAME$ " " LAST.NAME$
  327. PRINT "So let's FIELD it again:" : FIELD 1, 10 AS FIRST.NAME$
  328. LSET FIRST.NAME$="we done good this time"   'GET will replace this data.
  329. GET 1,1 : PRINT FIRST.NAME$ " " LAST.NAME$ : CLOSE : END
  330. '     (PgDn)
  331. 'page 15                      More About FIELDing
  332.  
  333. 'You can have several FIELD statements in affect at once. In fact, if you have
  334. 'done a GET and then do a FIELD, as above, the data that you have already
  335. 'retrieved will be assigned to the newly FIELDed variables without having to
  336. 'do a GET again, as shown by running example 6.
  337.  
  338. 'While it is obviously much easier to assign an entire record to one variable,
  339. 'it is easier to work with the data in the record by assigning each field to a
  340. 'seperate variable, so you often want to FIELD both types of variables:
  341. 'FIELD 1, 25 AS NAM$ : PRINT NAM$
  342. 'FIELD 1, 10 AS FIRST.NAME$, 15 AS LAST.NAME$
  343. 'PRINT LAST.NAME$ "," FIRST.NAME$
  344.  
  345. 'As shown in example 7, you don't have to FIELD an entire record if you are
  346. 'not interested in all of the data. If the fields you are interested in are
  347. 'not the first ones in the record, you can assign data to dummy variables:
  348. 'FIELD 1, 10 AS X$, 15 AS X$, 2 AS AGE$   or
  349. 'FIELD 1, 25 AS X$, 2 AS AGE$
  350. 'Notice that you can safely assign several fields to the same dummy variable.
  351.  
  352. '     (PgDn)
  353. 'page 16                 (More About FIELDing, cont.)
  354.  
  355. 'You can use loops, data statements and dummy variables to more easily FIELD
  356. 'long or complex records:
  357. 'X=0
  358. 'FOR I=1 TO 6
  359. '  READ FLD.LEN : FIELD 1, X AS X$, FLD.LEN AS FLD$(I) : X=X+FLD.LEN
  360. 'NEXT
  361. 'DATA 10, 15, 2, 3, 13, 2
  362. '      While it is helpful to have meaningful filenames, such as AGE$, it is
  363. 'sometimes advantageous to have subscripted FIELDed variables. For example,
  364. 'FOR I=1 TO 6 : LSET FLD$(I) = "" : NEXT  clears out all the variables much
  365. 'more easily than having to name each of the six variables individually.
  366.  
  367. 'In the example above, X is used to keep track of how much of the record has
  368. 'already been fielded, which, the first time through is zero. The second time
  369. 'through X=10, so the first 10 characters, which have already been assigned to
  370. 'FLD$(1), are assigned to a dummy variable and FLD.LEN, which is now 15, is
  371. 'assigned to FLD$(2). Since we don't have to FIELD the entire record, we can
  372. 'leave the rest of the record unfielded until the FIELDing loop gets to it
  373. 'by way of X being incremented each time.
  374. '     (PgDn)
  375. 'page 17                    Opening Non-Data Files
  376.  
  377. 'We've said that any file - even a program file - can be opened, read from and
  378. 'written to, and that any file can be read from or written to sequentially or
  379. 'as random access. To illustrate, we will now open and read from this program
  380. 'file itself. Notice that with the sequential read, we have to step through
  381. 'each "record" (in this case, a "record" is each line that ends with a
  382. 'carriage return and line feed), while with random access, we will jump
  383. 'directly to the "records" that contain the text of this page.
  384. example8:
  385. OPEN "TUTOR-04.BAS" FOR INPUT AS 1
  386. FOR I=1 TO 352: LINE INPUT #1, A$: PRINT I: NEXT
  387. FOR I=1 TO 22: LINE INPUT #1, A$: PRINT A$: NEXT : PRINT
  388. INPUT "NOW RANDOM ACCESS"; X$: CLOSE: PRINT
  389. OPEN "TUTOR-04.BAS" AS 1 LEN=1
  390. FIELD 1, 1 AS A$
  391. FOR I=17381 TO 18325
  392.   GET 1,I
  393.   IF ASC(A$)<>10 THEN PRINT A$; 'filter out the extra linefeed
  394. NEXT : CLOSE : PRINT : END
  395.  
  396. '     (PgDn)
  397. 'page 18                      Compressing Numbers
  398.  
  399. 'You may have noticed that only string data can be assigned to a FIELDed
  400. 'variable. That means that a number must be converted to a string before
  401. 'or during assigment:  LSET AGE$=STR$(AGE%).
  402.  
  403. 'In order to both save space and make it easier to maintain standard record
  404. 'length in random access files, numbers can be converted to a special compres-
  405. 'sed, fixed-length string data. (See MKI$, MKS$, and MKD$ in the manual.)
  406.  
  407. 'MKI$ converts an integer to a two-byte string; MKS$, a single-precision num-
  408. 'ber to a four-byte string; and MKD$, a double-precision number to an eight-
  409. 'byte string. The characters used in this string include any of the 255 ASCII
  410. 'characters, some of which look funny or are not printable at all when you
  411. 'try to view them on the screen:
  412. example9:
  413. N$=MKI$(1449) : PRINT "MKI$(1449) = " N$ : PRINT "LEN(N$) =" LEN(N$)
  414. PRINT "Converts back to" CVI(N$)  'CVI, CVS and CVD convert the strings back.
  415. N$=MKS$(12345+54321) : PRINT "MKS$(12345+54321) =" N$
  416. PRINT "LEN(N$) =" LEN(N$)
  417. PRINT "Converts back to" CVS(N$) : END
  418. '     (PgDn)
  419. 'page 19                       EOF, LOF and LOC
  420.  
  421. 'EOF is used to test for the last record of a file so that you do not attempt
  422. 'to read beyond the end of a file. eg: WHILE NOT EOF(1): GET 1: WEND
  423.  
  424. 'LOF lets you calculate the number of records in a random access file. To do
  425. 'this, divide the value of LOF by the defined record length of the file. eg:
  426. 'OPEN "DATAFILE" AS 1 LEN=125 : NUM.RECS = LOF(1) / 125
  427. 'FOR I=1 TO NUM.RECS : GET 1 : NEXT
  428.  
  429. 'LOC tells you where you are in a datafile, primarily used for random access
  430. 'files. eg: GET 1, 37 : PRINT LOC(1)  will print the number 37, since that is
  431. 'the number of the last record read or written. LOC is more useful if you
  432. 'don't know where in the file you are, which can happen if you do a series
  433. 'of GETs without record numbers (which causes the system to just get the
  434. 'next record in the file). For example:
  435. 'OPEN "MY-FILE.DTA" AS 1 LEN=55: FIELD 1, 25 AS NAM$, 5 AS ID$, 25 AS ADDR$
  436. 'WHILE ID$ <> "A1032" : GET 1 : WEND  'searches the file for ID# A1032
  437. 'LSET ID$="Z1032"        'changes the ID# to Z1032
  438. 'PUT 1, LOC(1)  'Just saying "PUT 1" would replace the data in the NEXT record
  439. '                  instead of the one just read.
  440. '(end of lesson 4)
  441.  
  442.  
  443.  
  444.  
  445. (end of file - press PgUp)
  446.